using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Particle3DSample;

namespace Space
{
    /// <summary>
    /// This is a game component that implements IUpdateable.
    /// </summary>
    public class DataComponent : Microsoft.Xna.Framework.GameComponent, IData
    {
        QuadTree<Ship> quadTree;

        List<SolarSystem> solarSystems = new List<SolarSystem>();
        public IEnumerable<SolarSystem> SolarSystems { get { return solarSystems; } }

        Player player;
        Player neutral;
        public Player NeutralPlayer { get { return neutral; } }

        List<Player> players = new List<Player>();
        public IEnumerable<Player> Players { get { return players; } }

        List<Ship> ships = new List<Ship>();
        public IEnumerable<Ship> Ships { get { return ships; } }

        List<Weapon> missiles = new List<Weapon>();
        public IEnumerable<Weapon> Missiles { get { return missiles; } }

        public Player MainPlayer { get { return player; } }

        ExplosionParticleSystem exposionSystem;

        int level;
        float clock;

        public DataComponent(Game game)
            : base(game)
        {
            // TODO: Construct any child components here
            Game.Services.AddService(typeof(IData), this);
        }

        /// <summary>
        /// Allows the game component to perform any initialization it needs to before starting
        /// to run.  This is where it can query for any required services and load content.
        /// </summary>
        public override void Initialize()
        {
            // TODO: Add your initialization code here
            exposionSystem = new ExplosionParticleSystem(Game, Game.Content);

            Game.Components.Add(exposionSystem);

            base.Initialize();
        }

        public void AddShip(Ship s)
        {
            //s.Fleet.Size++;
            ships.Add(s);
        }
        public void AddWeapon(Weapon w)
        {
            missiles.Add(w);
        }

        /// <summary>
        /// Allows the game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Update(GameTime gameTime)
        {
            if (Game.Services.GetService<IGameActions>().ProgramStage == Stage.Game)
            {
                float time = (float)gameTime.ElapsedGameTime.TotalSeconds;

                clock += time;

                if (quadTree == null)
                {
                    quadTree = new QuadTree<Ship>(solarSystems.Max(s=>s.Radius+s.Position.Length())*2);
                }
                quadTree.Reset();
                foreach (Ship s in ships)
                    quadTree.Insert(s);
                foreach (Player p in players)
                {
                    quadTree.Insert(p.FlagShip);
                    foreach(CapitalShip s in p.MainFleet)
                        quadTree.Insert(s);
                }

                IDataInput input = (IDataInput)Game.Services.GetService(typeof(IDataInput));

                if (input.IsKeyClicked(Keys.Escape))
                {
                    IGameActions ga = Game.Services.GetService<IGameActions>();
                    ga.ProgramStage = Stage.Menu;
                }

                if (input.LeftThumbstick != Vector2.Zero)
                {
                    player.FlagShip.TargetAngle = (float)Math.Atan2(-input.LeftThumbstick.Y, input.LeftThumbstick.X) - MathHelper.Pi;
                }
                else
                {
                    player.FlagShip.TargetAngle = player.FlagShip.Angle;
                }

                player.FlagShip.TargetSpeed = player.FlagShip.Speed * input.LeftThumbstick.Length();


                if (input.IsButtonDown(Buttons.RightTrigger) && (from ss in solarSystems orderby (ss.Position - player.FlagShip.Position).LengthSquared() select (ss.Position - player.FlagShip.Position).Length() > ss.Radius).First())
                {
                    player.FlagShip.Hyperdrive = true;
                    player.MainFleet.ForEach(s => s.Hyperdrive = true);
                }
                else
                {
                    player.FlagShip.Hyperdrive = false;
                    player.MainFleet.ForEach(s => s.Hyperdrive = false);
                }

                if (input.IsButtonPressed(Buttons.A))
                {
                    if (player.FlagShip.MainGun.CanFire)
                    {
                        player.FlagShip.MainGun.Fire();
                    }
                }

                if (input.IsButtonPressed(Buttons.Y))
                {
                    //player.PickTargets();
                    if (player.FlagShip.LaserGun.CanFire)
                    {
                        player.FlagShip.LaserGun.Target = player.MainFleet.Find(s => (s.Position - player.FlagShip.Position).Length() <= player.FlagShip.LaserGun.Range);
                        player.FlagShip.LaserGun.Fire();
                    }
                }

                if (input.IsButtonPressed(Buttons.B))
                {
                    if (player.FlagShip.BombGun.CanFire)
                    {
                        player.FlagShip.BombGun.Fire();
                    }
                }

                if (input.IsButtonPressed(Buttons.X))
                {
                    //foreach (SolarSystem s in SolarSystems)
                    //{
                    //    Planet p = s.Planets.Find(planet => planet.Player.Neutral && (planet.Position - MainPlayer.FlagShip.Position).Length() <= CapitalShip.ColonizeDistance);
                    //    if (p != null)
                    //    {
                    //        CapitalShip ship = MainPlayer.MainFleet.Find(sh => sh.Built && sh.Alive && sh.Colony);
                    //        if (ship != null)
                    //        {
                    //            ship.SetAction("colonize", p);
                    //        }
                    //        break;
                    //    }
                    //}
                }

                foreach (SolarSystem ss in solarSystems)
                {
                    foreach (Planet p in ss.Planets)
                    {
                        p.Update(time);
                    }
                    for (int i = ss.Buildings.Count-1; i >= 0; i--)
                    {
                        Building b = ss.Buildings[i];
                        if (!b.Alive)
                        {
                            ss.Buildings.RemoveAtInorder(i);
                        }
                        b.Update(time);
                    }
                }

                for (int i = 1; i < players.Count; i++)
                    players[i].Plot(time);


                //foreach (Ship ship in ships)
                foreach (Player p in players)
                {
                    p.Update(time);
                    for (int i = p.MainFleet.Count - 1; i >= 0; i--) {
                        Ship ship = p.MainFleet[i];
                        if (ship.Alive)
                        {
                            ship.Update(time);
                            ship.Push(p.MainFleet, time);
                            //var v = ships.Where(sp => sp.Player != ship.Player);
                            //foreach(Player _p in players.Where(pyer=>pyer!=p))
                            //    v = v.Concat(_p.MainFleet).Concat(_p.FlagShip);

                            //ship.PickTarget(v);

                            ship.PickTarget(quadTree.Find(ship.Position, ship.Range, s => s.Player != ship.Player));
                        }
                        else
                        {
                            p.MainFleet.RemoveAtInorder(i);
                            if(!ship.NoExplode)
                            for (int j = 0; j < 10;j++ )
                                exposionSystem.AddParticle(ship.Position, Vector3.Zero);
                        }
                    }
                }

                for (int i = ships.Count - 1; i >= 0; i--)
                {
                    Ship ship = ships[i];
                    ship.Update(time);
                    if(!ship.NoCollision)
                        ship.Push(ships, time);
                    ship.PickTarget(quadTree.Find(ship.Position, ship.Range, s => s.Player != ship.Player));
                    //solarSystems.ForEach(s =>
                    //{
                    //    ship.ConquerPlanet(s.Planets);
                    //});
                    if (!ship.Alive)
                    {
                        if (i != ships.Count - 1)
                        {
                            ships[i] = ships[ships.Count - 1];
                        }
                        ships.RemoveAt(ships.Count - 1);
                        //ship.Fleet.Size--;

                        if (!ship.NoExplode)
                        for (int j = 0; j < 5; j++)
                            exposionSystem.AddParticle(ship.Position, Vector3.Zero);
                    }
                }

                for (int i = missiles.Count - 1; i >= 0; i--)
                {
                    Weapon m = missiles[i];
                    m.Update(time);
                    if (!m.Alive) missiles.RemoveAt(i);
                }
                ICamera camera = Game.Services.GetService<ICamera>();

                exposionSystem.SetCamera(camera.View, camera.Projection);

                UpdateAction(time);

                if (level != 0)
                {
                    if (WinCondition())
                    {
                        level++;
                        CreateLevel(level);
                    }
                }
            }
            base.Update(gameTime);
        }
        float gravitationalConstant = 2;

        void Reset()
        {
            Game.Services.GetService<IState>().Reset();

            solarSystems.Clear();
            players.Clear();
            missiles.Clear();
            ships.Clear();
            quadTree = null;

            neutral = new Player(Game)
            {
                Neutral = true,
                Name = "Neutral",
                Color = Color.Gray
            };

            clock = 0;

            WinCondition = ConquerAllPlanets;
            UpdateAction = (time) => { };
        }

        public void CreateGame(int numSolar, int planetPerSolar, int playerPerSolar)
        {
            Reset();

            Random Rand = new Random();

            float magicConstant;
            //magicConstant = 12;
            magicConstant = 30;
            float solRadius;
            //solRadius = (float)Math.Sqrt(planetPerSolar) * magicConstant;

            float meanPlanetRadius = 1;
            solRadius = planetPerSolar * meanPlanetRadius * 4 * 4;

            float solDistFactor = 1;

            for (int i = 0; i < numSolar; i++)
            {
                Vector3 pos;
                do
                {
                    float angle = MathHelper.TwoPi * (float)Rand.NextDouble();
                    float dist = Rand.NextFloat() * solRadius * solDistFactor * (float)Math.Sqrt(4 * numSolar);
                    pos = new Vector3((float)Math.Cos(angle) * dist, 0, (float)Math.Sin(angle) * dist);
                } while (solarSystems.Any(ss => (ss.Position - pos).Length() < solRadius * solDistFactor));

                AddSolarSystem(pos, solRadius / 2);
            }

            string[] playerNames = { "unicode" };
            string[] homewordNames = { "Earth", "Hydra", "Carbo", "Narbe" };
            Color[] colors = { Color.Blue, Color.Red, Color.Magenta, Color.Yellow, Color.Green, Color.Pink, Color.Orange, Color.Brown };
            int playerCount = 0;
            foreach (SolarSystem ss in solarSystems)
            {
                for (int i = 0; i < playerPerSolar; i++)
                {
                    string playerName;
                    if (playerCount >= playerNames.Length) playerName = "CPU" + playerCount;
                    else playerName = playerNames[playerCount];

                    string homewordName;
                    if (playerCount >= homewordNames.Length) homewordName = "Homeword " + playerCount;
                    else homewordName = homewordNames[playerCount];

                    Color color;
                    if (playerCount >= colors.Length) color = Color.White;
                    else color = colors[playerCount];

                    Vector3 position;
                    float angle = i * MathHelper.TwoPi / playerPerSolar;
                    float dist = solRadius / 2/2;
                    position = new Vector3(Math2.Cos(angle) * dist, 0, Math2.Sin(angle) * dist) + ss.Position;

                    AddPlayer(solarSystem: ss, name: playerName, color: color, position: position, homeworld: homewordName);
                    if (playerCount == 0) player = players[0];
                    playerCount++;

                }
            }

            string[] nameList = {"Mars", "Mercury", "Aphla",
                                         "Ybredre", "Dnar", "Texn",
                                         "Eman", "Dlrmoh", "Rolco",
                                         "Tenpla", "Notopsi", "Tapl",
                                         "Reylpa", "Lutraln", "Volenti",
                                         "Depes", "Xenpll", "Eilless",
                                         "Tdet", "Smode", "Aet",
                                         "Phis", "Deola", "Syaol",
                                        "Dlod", "Mlic", "Sevs"};
            foreach (SolarSystem ss in solarSystems)
            {
                for (int i = 0; i < planetPerSolar; i++)
                {
                    AddPlanet(solarSystem: ss, name: (from name in nameList
                                                      where (!solarSystems.Any(s => s.Planets.Any(p => p.Name == name)))
                                                      orderby Rand.Next()
                                                      select name).First(), player: neutral);
                }
            }

            Vector3 target = solarSystems.Find(s => s.Planets.Any(p => p.Player == player)).Planets.Find(p => p.Player == player).Position;

            Vector3 cam = target + new Vector3(0, 50, 0);
            Vector3 up = new Vector3(0, 0, 1);

            ICamera camera = Game.Services.GetService<ICamera>();
            camera.Position = cam;
            camera.Target = target;
            camera.Up = up;

            WinCondition = ConquerAllPlanets;
        }

        void AddSolarSystem(Vector3 position, float radius)
        {
            SolarSystem solsys = new SolarSystem()
            {
                Position = position,
                Radius = radius
            };
            solsys.Model = Game.Content.Load<Model>("models/ball");
            solsys.Texture = Game.Content.Load<Texture2D>("textures/sun");

            solarSystems.Add(solsys);
        }

        public void AddPlayer(SolarSystem solarSystem, string name, Color color, Vector3 position, string homeworld)
        {
            Player p2 = new Player(Game)
            {
                Neutral = false,
                Name = name,
                Color = color
            };
            players.Add(p2);
            Planet pl2 = new Planet(Game)
            {
                SolarSystem = solarSystem,
                Type = "earth",
                RotateSpeed = MathHelper.Pi / 100,
                Position = position,
                Player = p2,
                Name = homeworld,
                Population = 20,
                ShipSupportNum = 20,
                Model = Game.Content.Load<Model>("models/ball"),
                Texture = Game.Content.Load<Texture2D>("textures/earth"),
                Radius = 0.5f,
            };

            p2.FlagShip = FlagShip.Create(Game, level: 1);
            p2.FlagShip.Player = p2;

            p2.AddPlanet(pl2);
            solarSystem.Planets.Add(pl2);
        }

        public void AddPlayer(string name, Color color)
        {
            Player p2 = new Player(Game)
            {
                Neutral = false,
                Name = name,
                Color = color
            };
            players.Add(p2);

            p2.FlagShip = FlagShip.Create(Game, level: 1);
            p2.FlagShip.Player = p2;
        }

        public void AddPlanet(SolarSystem solarSystem, Vector3 position, string name, Player player, float productionInterval = 1, int population = -1)
        {
            Random Rand = new Random();

            if (population == -1)
                population = Rand.Next(12) + 3;
            Planet planet = new Planet(Game)
            {
                SolarSystem = solarSystem,
                Type = (from o in (new string[] { "lava", "dirt", "desert", "snow" })
                        orderby Rand.Next()
                        select o).First(),
                Name = name,
                Player = player,
                Population = population,
                Position = position,
                RotateSpeed = (float)Rand.NextDouble() * MathHelper.Pi / 50 + MathHelper.Pi / 100,
                Model = Game.Content.Load<Model>("models/ball"),
                Radius = 0.5f,
            };
            planet.Texture = Game.Content.Load<Texture2D>("textures/" + planet.Type);
            solarSystem.Planets.Add(planet);

        }

        public void AddPlanet(SolarSystem solarSystem, string name, Player player, float productionInterval = 1, int population = 0)
        {
            Random Rand = new Random();

            float threshold = 25;
            float sunThreshold = 2;
            float orbitThreshold = 4;
            Vector3 Pos;
            int iterations = 0;
            do
            {
                iterations++;
                if (iterations > 50) threshold--;
                float angle = (float)Rand.NextDouble() * MathHelper.TwoPi;
                float dist = (float)Math.Sqrt(Rand.NextDouble()) * solarSystem.Radius;
                Pos = new Vector3((float)Math.Cos(angle) * dist, 0, (float)Math.Sin(angle) * dist) + solarSystem.Position;
            } while (solarSystem.Planets.Any(p =>
                        (p.Position - Pos).Length() < threshold
                        || Math.Abs((p.Position - solarSystem.Position).Length() - (Pos - solarSystem.Position).Length()) < orbitThreshold)

                        || (solarSystem.Position - Pos).Length() < sunThreshold);

            AddPlanet(solarSystem, Pos, name, player, productionInterval, population);
        }

        public void CreateLevel(int level)
        {
            Reset();
            this.level = level;
            switch (level)
            {
                case 1:
                    SetLevel_1();
                    break;
                case 2:
                    SetLevel_2();
                    break;
            }
        }

        public void SetLevel_1()
        {
            AddSolarSystem(Vector3.Zero, 20);
            SolarSystem ss = solarSystems[0];
            string playerName = "player";
            Color color = Color.Blue;
            Vector3 position = new Vector3(5, 0, 5);
            string homewordName = "Earth";

            AddPlayer(solarSystem: ss, name: playerName, color: color, position: position, homeworld: homewordName);
            player = players[0];

            string name = "Mars";

            AddPlanet(solarSystem: ss, position: new Vector3(10, 0, 10), name: name, player: neutral);

            Vector3 target = solarSystems.Find(s => s.Planets.Any(p => p.Player == player)).Planets.Find(p => p.Player == player).Position;

            Vector3 cam = target + new Vector3(0, 50, 0);
            Vector3 up = new Vector3(0, 0, 1);

            ICamera camera = Game.Services.GetService<ICamera>();
            camera.Position = cam;
            camera.Target = target;
            camera.Up = up;

            WinCondition = ConquerAllPlanets;
        }
        public void SetLevel_2()
        {
            AddSolarSystem(Vector3.Zero, 20);
            SolarSystem ss = solarSystems[0];
            string playerName = "player";
            Color color = Color.Blue;
            Vector3 position = new Vector3(5, 0, 5);
            string homewordName = "Earth";

            AddPlayer(solarSystem: ss, name: playerName, color: color, position: position, homeworld: homewordName);
            player = players[0];

            AddPlayer("CPU", Color.Red);
            players[1].Credit = 1000000;

            string name = "Mars";

            AddPlanet(solarSystem: ss, position: new Vector3(10, 0, 10), name: name, player: neutral);

            Vector3 target = solarSystems.Find(s => s.Planets.Any(p => p.Player == player)).Planets.Find(p => p.Player == player).Position;

            Vector3 cam = target + new Vector3(0, 50, 0);
            Vector3 up = new Vector3(0, 0, 1);

            ICamera camera = Game.Services.GetService<ICamera>();
            camera.Position = cam;
            camera.Target = target;
            camera.Up = up;

            WinCondition = ConquerAllPlanets;

            UpdateAction = (time) =>
            {
                float[] spawnTimes = { 1, 2, 3, 4, 5, 6 };

                foreach (float spawnTime in spawnTimes)
                {
                    if (clock >= spawnTime && clock - time < spawnTime)
                    {
                        players[1].BuyShip("corvette");
                    }
                }
            };
        }
        Action<float> UpdateAction;
        Win WinCondition;
        delegate bool Win();
        bool ConquerAllPlanets()
        {
            foreach (SolarSystem s in solarSystems)
                if (s.Planets.Exists(p => p.Player != MainPlayer))
                    return false;
            return true;
        }
    }
}
